
/*
* Project Name: wxpay-trunk
* File Name: SessionTimeoutAuthenticationFilter.java
* Class Name: SessionTimeoutAuthenticationFilter
*
* Copyright 2015 kandinfo Software Inc
*
* Licensed under the tangqh
*
* http://www.tangqh.com
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
	
package com.kandinfo.base.security.authc;

import java.io.IOException;
import java.util.Date;
import java.util.Enumeration;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.http.HttpStatus;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kandinfo.base.common.constant.ApplicationConstant;
import com.kandinfo.base.common.constant.ResultCode;
import com.kandinfo.base.common.dto.ResultDto;
import com.kandinfo.base.common.util.ApplicationContextUtil;
import com.kandinfo.base.common.util.MessageUtil;
import com.kandinfo.base.common.util.web.WebUtil;
import com.kandinfo.base.security.SecurityContext;
import com.kandinfo.base.security.domain.User;
import com.kandinfo.base.security.service.UserService;

/**
 * Class Name: SessionTimeoutAuthenticationFilter
 * 
 * @author SC
 *
 */
public class SessionTimeoutAuthenticationFilter extends FormAuthenticationFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(SessionTimeoutAuthenticationFilter.class);
    private static final String USER_KEY = "shiro.user";
    public static final String SESSION_TIMEOUT = SessionTimeoutAuthenticationFilter.class + "_session_timeout";

    private static final String SESSION_TIMEOUT_MSG = "error.common.session_timeout";

    // only part of session id is enough for identification
    private static final int SESSION_ID_SECTION_LEN = 15;

    private static final String DATE_FORMAT = "MM-dd HH:mm:ss";

    @Override
    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        saveRequest(request);
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        if (WebUtil.isAjaxRequest(req)) {
            ObjectMapper objectMapper = new ObjectMapper();
            res.setContentType("application/json;charset=UTF-8");
            res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            ResultDto error = new ResultDto();
            error.setCode(ResultCode.SESSION_TIME_OUT);
            error.setMessage(MessageUtil.getMessage(SESSION_TIMEOUT_MSG));
            objectMapper.writeValue(response.getWriter(), error);
            LOGGER.debug("session time out for ajax request:{}", req.getRequestURI());
        } else {
            LOGGER.debug("session time out for request:{}", req.getRequestURI());
            req.getSession().setAttribute(SESSION_TIMEOUT, true);
            redirectToLogin(request, response);
        }
        HttpSession session = req.getSession(false);
        if (session != null) {
            LOGGER.debug(
                    "session time out with id: {}, is sesion new:{}, started: {}, last accessed: {}, request headers: {}, ip:{}",
                    session.getId(),
                    session.isNew(),
                    DateFormatUtils.format(session.getCreationTime(), DATE_FORMAT),
                    DateFormatUtils.format(session.getLastAccessedTime(), DATE_FORMAT),
                    getHeaderString(request), request.getRemoteAddr());
        } else {
            LOGGER.debug("session time out, no session available for current request");
        }
    }

    private String getHeaderString(ServletRequest request) {
        HttpServletRequest req = (HttpServletRequest) request;
        StringBuilder sb = new StringBuilder();
        Enumeration<?> headerNames = req.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = (String) headerNames.nextElement();
            sb.append("[").append(headerName).append("=").append(req.getHeader(headerName)).append("]");
        }
        return sb.toString();
    }

    @Override
    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { // NOSONAR
        boolean res = super.onPreHandle(request, response, mappedValue);
        if (SecurityContext.getInstance().isAuthenticated()) {
            MDC.put(ApplicationConstant.LOG_USER_NAME, SecurityContext.getInstance().getCurrentUser().getUsername());
        }

        String sessionId = ((HttpServletRequest) request).getSession(false).getId();
        if (sessionId != null) {
            MDC.put(ApplicationConstant.LOG_SESSION_ID,
                    sessionId.substring(sessionId.length() - SESSION_ID_SECTION_LEN));
        }
        return res;
    }

    @Override
    protected void cleanup(ServletRequest request, ServletResponse response, Exception existing)
            throws ServletException, IOException {
        MDC.remove(ApplicationConstant.LOG_USER_NAME);
        MDC.remove(ApplicationConstant.LOG_SESSION_ID);
        super.cleanup(request, response, existing);
    }

    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
            ServletRequest request, ServletResponse response) throws Exception { // NOSONAR
        HttpServletRequest req = (HttpServletRequest) request;
        LOGGER.info("{} login complete, session ID: {} ", subject.getPrincipal(), subject.getSession().getId());
        StringBuilder sb = new StringBuilder();
        Enumeration<?> attriNames = req.getAttributeNames();
        while (attriNames.hasMoreElements()) {
            String attriName = (String) attriNames.nextElement();
            sb.append("[").append(attriName).append("=").append(req.getAttribute(attriName)).append("]");
        }
        LOGGER.info("session attributes: {}", sb);
        return super.onLoginSuccess(token, subject, request, response);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (super.isAccessAllowed(request, response, mappedValue)) {
            return true;
        } else {
            Subject subject = getSubject(request, response);
            if (subject.isRemembered()) {
                if(null == subject.getSession().getAttribute(USER_KEY)){
                    UserService userService =  ApplicationContextUtil.getBean(UserService.class);
                   
                    User loginUser = userService.findUserById(Long.valueOf(subject.getPrincipal().toString()));
                    try{
                        loginUser.setLastLoginTs(new Date());
                        userService.save(loginUser);
                    }catch(Exception e){
                        LOGGER.warn("error to update user.", e);
                    }
                    subject.getSession().setAttribute(USER_KEY, loginUser);
                }
                return true;
            }
            return false;
        }
    }

}
